﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using Tao.Sdl;
using Tao.OpenGl;
using Tao.DevIl;

namespace Ommf
{
    public class GraphicsManager
    {
        private OmmfEngine engine;
        private Timer timer;
        private IntPtr surface;

        private List<Image> images = new List<Image>();
        private List<TrueTypeFont> fonts = new List<TrueTypeFont>();
        private IPrintable printFont;
        private int viewportWidth = 640;
        private int viewportHeight = 480;

        internal GraphicsManager(OmmfEngine engine)
        {
            this.engine = engine;
            timer = engine.Timer;
        }

        public void CreateWindow()
        {
            CreateWindow(viewportWidth, viewportHeight, this.Caption, true);
        }

        public void CreateWindow(int width, int height)
        {
            CreateWindow(width, height, this.Caption, true);
        }

        public void CreateWindow(int width, int height, string caption)
        {
            CreateWindow(width, height, caption, true);
        }

        public void CreateWindow(int width, int height, string caption, bool vsync)
        {
            viewportWidth = width;
            viewportHeight = height;
            this.Caption = caption;
            
            Sdl.SDL_GL_SetAttribute(Sdl.SDL_GL_DOUBLEBUFFER, 1);
            Sdl.SDL_GL_SetAttribute(Sdl.SDL_GL_SWAP_CONTROL, vsync ? 1 : 0);
            surface = Sdl.SDL_SetVideoMode(viewportWidth, viewportHeight, 32, Sdl.SDL_OPENGL | Sdl.SDL_HWSURFACE);
            
            Il.ilInit();
            InitializeGL();

            engine.IsOpened = true;
        }

        private void InitializeGL()
        {
            Gl.glEnable(Gl.GL_TEXTURE_2D);

            Gl.glEnable(Gl.GL_BLEND);
            Gl.glBlendFunc(Gl.GL_SRC_ALPHA, Gl.GL_ONE_MINUS_SRC_ALPHA);

            Gl.glClearColor(0, 0, 0, 1.0f);
            Gl.glViewport(0, 0, viewportWidth, viewportHeight);

            Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);

            Gl.glMatrixMode(Gl.GL_PROJECTION);
            Gl.glLoadIdentity();

            Gl.glOrtho(0, viewportWidth, viewportHeight, 0, -1.0, 1.0);

            Gl.glMatrixMode(Gl.GL_MODELVIEW);
            Gl.glLoadIdentity();
        }

        internal void Close()
        {
            foreach (Image image in images)
            {
                if (!image.IsDisposed)
                    image.Dispose();
            }

            images.Clear();

            foreach (TrueTypeFont font in fonts)
            {
                if (!font.IsDisposed)
                    font.Dispose();
            }

            fonts.Clear();
        }

        public Image CreateImage(string filename)
        {
            if (!engine.IsOpened) return null;

            Image image = new Image(filename);
            images.Add(image);
            return image;
        }

        public Animation CreateAnimation(Image image, int frameWidth, int frameHeight, int numFrames, double delay)
        {
            if (!engine.IsOpened) return null;

            return new Animation(image, frameWidth, frameHeight, numFrames, delay);
        }

        public Batch CreateBatch(Image image)
        {
            if (!engine.IsOpened) return null;

            return new Batch(image);
        }

        public TrueTypeFont CreateFont(string filename, float pointSize)
        {
            return CreateFont(filename, pointSize, ImageFilter.Linear);
        }

        public TrueTypeFont CreateFont(string filename, float pointSize, ImageFilter filter)
        {
            if (!engine.IsOpened) return null;

            TrueTypeFont font = new TrueTypeFont(filename, pointSize, filter);
            fonts.Add(font);
            return font;
        }

        public TileFont CreateTileFont(Image image, int tileWidth, int tileHeight, string charset)
        {
            if (!engine.IsOpened) return null;

            return new TileFont(image, tileWidth, tileHeight, charset);
        }

        public void SetColor(Color color)
        {
            Gl.glColor4ub(color.R, color.G, color.B, color.A);
        }

        public void SetColor(byte r, byte g, byte b)
        {
            Gl.glColor3ub(r, g, b);
        }

        public void SetColor(byte r, byte g, byte b, byte a)
        {
            Gl.glColor4ub(r, g, b, a);
        }

        public void SetBackColor(Color color)
        {
            Gl.glClearColor(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f, 1.0f);
        }

        public void SetBackColor(byte r, byte g, byte b)
        {
            Gl.glClearColor(r / 255.0f, g / 255.0f, b / 255.0f, 1.0f);
        }

        public void Draw(IDrawable drawObject, float x, float y)
        {
            Draw(drawObject, x, y, 0, 1, 1);
        }

        public void Draw(IDrawable drawObject, float x, float y, float angle)
        {
            Draw(drawObject, x, y, angle, 1, 1);
        }

        public void Draw(IDrawable drawObject, float x, float y, float angle, float scaleX, float scaleY)
        {
            drawObject.Draw(x, y, angle, scaleX, scaleY);
        }

        public void Draw(IDrawable drawObject, float x, float y, float angle, float scaleX, float scaleY, float subX, float subY, float subWidth, float subHeight)
        {
            drawObject.DrawSubrect(x, y, angle, scaleX, scaleY, subX, subY, subWidth, subHeight);
        }

        public void DrawPoint(float x, float y)
        {
            Gl.glDisable(Gl.GL_TEXTURE_2D);

            Gl.glBegin(Gl.GL_POINTS);
            Gl.glVertex3f(x, y, 0);
            Gl.glEnd();

            Gl.glEnable(Gl.GL_TEXTURE_2D);
        }

        public void DrawLine(float x1, float y1, float x2, float y2)
        {
            Gl.glDisable(Gl.GL_TEXTURE_2D);

            Gl.glBegin(Gl.GL_LINES);
            Gl.glVertex3f(x1, y1, 0);
            Gl.glVertex3f(x2, y2, 0);
            Gl.glEnd();

            Gl.glEnable(Gl.GL_TEXTURE_2D);
        }

        public void DrawRectangle(bool fill, float x, float y, float width, float height)
        {
            Gl.glDisable(Gl.GL_TEXTURE_2D);

            Gl.glBegin(fill ? Gl.GL_QUADS : Gl.GL_LINE_LOOP);
            
            Gl.glVertex3f(x, y, 0);
            Gl.glVertex3f(x, y + height, 0);
            Gl.glVertex3f(x + width, y + height, 0);
            Gl.glVertex3f(x + width, y, 0);
            
            Gl.glEnd();

            Gl.glEnable(Gl.GL_TEXTURE_2D);
        }

        public void Print(string text, float x, float y)
        {
            Print(text, x, y, 0, 1, 1);
        }

        public void Print(string text, float x, float y, float angle)
        {
            Print(text, x, y, angle, 1, 1);
        }

        public void Print(string text, float x, float y, float angle, float scaleX, float scaleY)
        {
            printFont.Print(text, x, y, angle, scaleX, scaleY);
        }

        public void Present()
        {
            Sdl.SDL_GL_SwapBuffers();
            
            Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);
            Gl.glLoadIdentity();

            engine.PollEvents();
            timer.Delay();
        }

        public void Translate(float x, float y)
        {
            Gl.glTranslatef(x, y, 0);
        }

        public void Scale(float x, float y)
        {
            Gl.glScalef(x, y, 1);
        }

        public void Rotate(float angle)
        {
            Gl.glRotatef(angle, 0, 0, 1);
        }

        #region Properties

        public IPrintable PrintFont
        {
            get { return printFont; }
            set { printFont = value; }
        }

        public Color ForeColor
        {
            get
            {
                float[] color = new float[4];
                Gl.glGetFloatv(Gl.GL_CURRENT_COLOR, color);
                
                return Color.FromArgb(
                    (int)(color[3] * 255.0f),
                    (int)(color[0] * 255.0f),
                    (int)(color[1] * 255.0f),
                    (int)(color[2] * 255.0f));
            }
            set { SetColor(value); }
        }

        public Color BackColor
        {
            get
            {
                float[] color = new float[4];
                Gl.glGetFloatv(Gl.GL_COLOR_CLEAR_VALUE, color);

                return Color.FromArgb(
                    (int)(color[3] * 255.0f),
                    (int)(color[0] * 255.0f),
                    (int)(color[1] * 255.0f),
                    (int)(color[2] * 255.0f));
            }
            set { SetBackColor(value); }
        }

        public float LineWidth
        {
            get
            {
                float w;
                Gl.glGetFloatv(Gl.GL_LINE_WIDTH, out w);
                return w;
            }
            set { Gl.glLineWidth(value); }
        }

        public LineStyle LineStyle
        {
            get
            {
                return Gl.glIsEnabled(Gl.GL_LINE_SMOOTH) == Gl.GL_TRUE ? LineStyle.Smooth : LineStyle.Rough;
            }
            set
            {
                switch (value)
                {
                    case LineStyle.Rough:
                        Gl.glDisable(Gl.GL_LINE_SMOOTH);
                        break;

                    case LineStyle.Smooth:
                        Gl.glEnable(Gl.GL_LINE_SMOOTH);
                        Gl.glHint(Gl.GL_LINE_SMOOTH_HINT, Gl.GL_NICEST);
                        break;
                }
            }
        }

        public int FramerateLimit
        {
            get { return timer.FramerateLimit; }
            set { timer.FramerateLimit = value; }
        }

        public string Caption
        {
            get
            {
                string caption, icon;
                Sdl.SDL_WM_GetCaption(out caption, out icon);
                return caption;
            }
            set { Sdl.SDL_WM_SetCaption(value, value); }
        }

        public int ViewportWidth
        {
            get { return viewportWidth; }
            set { viewportWidth = value; }
        }

        public int ViewportHeight
        {
            get { return viewportHeight; }
            set { viewportHeight = value; }
        }

        #endregion
    }
}
